#!/bin/bash
#
# mdio-util - MDIO Utility for AST2600
#
# This shell script provides the ability to access MDIO bus over devmem
#
# TODO: A read/write action contains multiple MDIO operations, it needs a 
#       lock to protect entire action.


RETRY_CNT=30
RETRY_DEALY_SEC="0.1"

MDIO_BASE_REG=$((16#1e650000))
C22_READ_CMD=$((16#98000000))
C22_WRITE_CMD=$((16#94000000))

C45_ADDR_CMD=$((16#80000000))
C45_WRITE_CMD=$((16#84000000))
C45_PREAD_CMD=$((16#88000000))
C45_READ_CMD=$((16#8C000000))

PHY_ADDR_STEP=$((16#200000))
DEV_ADDR_STEP=$((16#10000))
REG_ADDR_STEP=$((16#10000))

aspeed_mdio_c22_read() {
    BUS=$1
    PHY_ADDR=$2
    REG_ADDR=$3

    MDIO_CTRL_REG=$((MDIO_BASE_REG+BUS*8))
    MDIO_DATA_REG=$((MDIO_CTRL_REG+4))

    CMD=$((C22_READ_CMD+PHY_ADDR*PHY_ADDR_STEP+REG_ADDR*REG_ADDR_STEP))

    # write control register
    devmem $MDIO_CTRL_REG 32 $CMD
    for i in $(seq 1 1 $RETRY_CNT)
    do
        CHECK_STR=$(devmem $MDIO_CTRL_REG)
        CHECK_VAL=$((16#${CHECK_STR#"0x"}))
        if [ $((CHECK_VAL&16#80000000)) -eq 0 ]; then
            break
        elif [ "$i" -eq "${RETRY_CNT}" ]; then
            echo "[c22] read operation timeout"
            return 1
        fi        
        sleep "$RETRY_DEALY_SEC"
    done

    # read data register
    for i in $(seq 1 1 $RETRY_CNT)
    do
        DATA_STR=$(devmem $MDIO_DATA_REG)
        DATA_VAL=$((16#${DATA_STR#"0x"}))
        if [ $((DATA_VAL&16#00010000)) -ne 0 ]; then
            DATA_VAL=$((DATA_VAL&16#0000FFFF))
            echo "0x$(printf %04x $DATA_VAL)"
            break
        elif [ "$i" -eq "$RETRY_CNT" ]; then
            echo "[c22] read data timeout"
            return 1
        fi
        sleep "$RETRY_DEALY_SEC"
    done

    return 0
}

aspeed_mdio_c22_write() {
    BUS=$1
    PHY_ADDR=$2
    REG_ADDR=$3
    WR_DATA=$4

    MDIO_CTRL_REG=$((MDIO_BASE_REG+BUS*8))
    MDIO_DATA_REG=$((MDIO_CTRL_REG+4))
    CMD=$((C22_WRITE_CMD+PHY_ADDR*PHY_ADDR_STEP+REG_ADDR*REG_ADDR_STEP+WR_DATA))

    # write control register
    devmem $MDIO_CTRL_REG 32 $CMD
    for i in $(seq 1 1 $RETRY_CNT)
    do
        CHECK_STR=$(devmem $MDIO_CTRL_REG)
        CHECK_VAL=$((16#${CHECK_STR#"0x"}))
        if [ $((CHECK_VAL&16#80000000)) -eq 0 ]; then
            break
        elif [ "$i" -eq "$RETRY_CNT" ]; then
            echo "[c22] write operation timeout"
            return 1
        fi
        sleep "$RETRY_DEALY_SEC"
    done

    return 0
}

aspeed_mdio_c45_ctrl() {
    BUS=$1
    C45_OP=$2
    PHY_ADDR=$3
    DEV_ADDR=$4
    CMD_DATA=$5
    
    MDIO_CTRL_REG=$((MDIO_BASE_REG+BUS*8))

    case "$C45_OP" in
        0)
            CMD=$C45_ADDR_CMD
            ;;
        1)
            CMD=$C45_WRITE_CMD
            ;;
        2)
            CMD=$C45_PREAD_CMD
            ;;
        3)
            CMD=$C45_READ_CMD
            ;;
        *)
            echo "[c45] unknow operator $C45_OP"
            return 1
            ;;
    esac

    CMD=$((CMD+PHY_ADDR*PHY_ADDR_STEP+DEV_ADDR*DEV_ADDR_STEP+CMD_DATA))

    # write control register
    devmem $MDIO_CTRL_REG 32 $CMD
    for i in $(seq 1 1 $RETRY_CNT)
    do
        CHECK_STR=$(devmem $MDIO_CTRL_REG)
        CHECK_VAL=$((16#${CHECK_STR#"0x"}))
        if [ $((CHECK_VAL&16#80000000)) -eq 0 ]; then
            break
        elif [ "$i" -eq "$RETRY_CNT" ]; then
            echo "[c45] operation timeout, op: $C45_OP"
            return 1
        fi
        sleep "$RETRY_DEALY_SEC"
    done

    return 0
}

aspeed_mdio_c45_data() {
    BUS=$1
    PHY_ADDR=$2
    DEV_ADDR=$3

    MDIO_CTRL_REG=$((MDIO_BASE_REG+BUS*8))
    MDIO_DATA_REG=$((MDIO_CTRL_REG+4))

    # read data register
    for i in $(seq 1 1 $RETRY_CNT)
    do
        DATA_STR=$(devmem $MDIO_DATA_REG)
        DATA_VAL=$((16#${DATA_STR#"0x"}))
        if [ $((DATA_VAL&16#00010000)) -ne 0 ]; then
            DATA_VAL=$((DATA_VAL&16#0000FFFF))
            echo "0x$(printf %04x $DATA_VAL)"
            break
        elif [ "$i" -eq "$RETRY_CNT" ]; then
            echo "[c45] read data timeout"
            return 1
        fi
        sleep "$RETRY_DEALY_SEC"
    done

    return 0
}

print_usage() {
    echo "Usage:"
    echo "    [clause 22]"
    echo "        $0 c22 r <BUS> <PHY_ADDR> <REG_ADDR>"
    echo "        $0 c22 w <BUS> <PHY_ADDR> <REG_ADDR> <DATA>"
    echo "    [clause 45]"
    echo "        $0 c45 r <BUS> <PHY_ADDR> <DEV_ADDR> <REG_ADDR>"
    echo "        $0 c45 w <BUS> <PHY_ADDR> <DEV_ADDR> <REG_ADDR> <DATA>"
}

CLAUSE_VER=${1}
CMD_TYPE=${2}
BUS=$3
PHY_ADDR=$4

if [ ! -c /dev/mem ]; then
    mknod /dev/mem c 1 1
fi

if [ "$CLAUSE_VER" == "c22" ] && [ "$CMD_TYPE" == "r" ] && [ "$#" -eq 5 ]; then
    REG_ADDR=${5}
    aspeed_mdio_c22_read "$BUS" "$PHY_ADDR" "$REG_ADDR" || exit 1
elif [ "$CLAUSE_VER" == "c22" ] && [ "$CMD_TYPE" == "w" ] && [ "$#" -eq 6 ]; then
    REG_ADDR=${5}
    DATA=${6}
    aspeed_mdio_c22_write "$BUS" "$PHY_ADDR" "$REG_ADDR" "$DATA" || exit 1
elif [ "$CLAUSE_VER" == "c45" ] && [ "$CMD_TYPE" == "r" ] && [ "$#" -eq 6 ]; then
    DEV_ADDR=${5}
    REG_ADDR=${6}
    aspeed_mdio_c45_ctrl "$BUS" 0 "$PHY_ADDR" "$DEV_ADDR" "$REG_ADDR" || exit 1
    aspeed_mdio_c45_ctrl "$BUS" 3 "$PHY_ADDR" "$DEV_ADDR" "$REG_ADDR" || exit 1
    aspeed_mdio_c45_data "$BUS" "$PHY_ADDR" "$DEV_ADDR" || exit 1
elif [ "$CLAUSE_VER" == "c45" ] && [ "$CMD_TYPE" == "w" ] && [ "$#" -eq 7 ]; then
    DEV_ADDR=${5}
    REG_ADDR=${6}
    DATA=${7}
    aspeed_mdio_c45_ctrl "$BUS" 0 "$PHY_ADDR" "$DEV_ADDR" "$REG_ADDR" || exit 1
    aspeed_mdio_c45_ctrl "$BUS" 1 "$PHY_ADDR" "$DEV_ADDR" "$DATA" || exit 1
else
    print_usage
fi
